package optimizer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;


public class Optimizer {

    HashMap<Integer, String> lineMap;
    HashMap<Integer, Boolean> ignoreLineMap;
    HashMap<String, Integer> labelMap;
    HashMap<Integer, Instruction> instructionMap;
    HashMap<Integer, String> labelScope;
    ArrayList<Integer> branchInstructionList;
    private int counter = 0;

    public Optimizer(String st) {
        lineMap = new HashMap<Integer, String>();  // to store original Program
        ignoreLineMap = new HashMap<Integer, Boolean>();    // Indicator  to Ignore Comments and data structures.
        labelMap = new HashMap<String, Integer>();          // Set of Labels
        instructionMap = new HashMap<Integer, Instruction>(); // Instruction map
        labelScope = new HashMap<Integer, String>();   // Label Scape
        branchInstructionList = new ArrayList<Integer>();
        readProgram(st);
        //printOptimizedCode();
    }

    private void readProgram(String st) {
        Scanner scanner = new Scanner(st);
        String line;
        Instruction inst;
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();
            if (line.trim().length() <= 0 || line.trim().startsWith(";"))    // Ignore Empty Lines and Comments
            {
                continue;
            }

            counter++;

            // Avoiding Data and other syntax of the Grammar.
            if (line.contains(".")) {
                lineMap.put(counter, line);
                ignoreLineMap.put(counter, true);
                labelScope.put(counter, "");
                continue;
            }

            //Checking for labels ;
            if (line.contains(":"))  //Assumptions:- all labels are separated from instructions. ":" doesn't occurs in other instructions.
            {   lineMap.put(counter, line);
                String label = line.trim().split(":")[0];
                labelMap.put(label, counter);
                labelScope.put(counter, label);
                ignoreLineMap.put(counter, true);
                continue;
            }

            ignoreLineMap.put(counter, false);
            lineMap.put(counter, "\t\t" + line);
            //Identify grammars Supports: add, lw, sw, j, slt, seq,, bnez
            inst = getInstruction(line);
            instructionMap.put(counter, inst);
            labelScope.put(counter, labelScope.get((counter - 1)));


            // Inline Jump Optimization
            if (inst.getType() == 3) {
                optimizeJump(counter);
            } else if (inst.getType() == 4) {
                branchInstructionList.add(counter);
            }
        }

    }

    private void optimizeJump(int lineNumber) {
        int traceBack = lineNumber;
        boolean notSwapped = true;
        while (notSwapped) {
            traceBack--;
            if(branchInstructionList.contains(traceBack))
            {
                break;
            }
            if (labelScope.get(traceBack).equals(labelScope.get(lineNumber))) {
                notSwapped = !(swapInstruction(lineNumber, traceBack));
            } else {
                break;
            }
        }
    }

    private synchronized boolean swapInstruction(int key1, int key2) {
        if (ignoreLineMap.get(key1) || ignoreLineMap.get(key2)) {
            return false;
        }
        Instruction temp = instructionMap.get(key1);
        instructionMap.put(key1, instructionMap.get(key2));
        instructionMap.put(key2, temp);
        String tempLine = lineMap.get(key1);
        lineMap.put(key1, lineMap.get(key2));
        lineMap.put(key2, tempLine);
        return true;
    }


    public String getOptimizedCode() {
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= counter; i++) {
            sb.append(lineMap.get(i)).append("\n");
        }
        return sb.toString();
    }

    private void printOptimizedCode() {
        for (int i = 1; i <= counter; i++) {
            if (ignoreLineMap.get(i)) {
                System.out.println(i + ") " + ignoreLineMap.get(i) + "\t" + labelScope.get(i) );
            } else {
                System.out.println(i + ") " + ignoreLineMap.get(i) + "\t" + labelScope.get(i) + "\t\t\t" + instructionMap.get(i).toString());
            }

        }
    }

    private Instruction getInstruction(String line) {
        int oper = -1;
        Instruction inst = null;
        String[] grammar = line.trim().split("\\W+");
        String op = grammar[0];
        if (op.equalsIgnoreCase("lw")) {  // lw      r1,     i
            oper = 1;
            inst = new Instruction(oper, op, grammar[1], grammar[2]);
        } else if (op.equalsIgnoreCase("sw")) //  sw      i,      r1
        {
            oper = 2;
            inst = new Instruction(oper, op, grammar[1], grammar[2]);
        } else if (op.equalsIgnoreCase("j"))  // j main_loop_1
        {
            oper = 3;
            inst = new Instruction(oper, op, grammar[1]);
        } else if (op.equalsIgnoreCase("beqz") || op.equalsIgnoreCase("bnez")) // bnqz    r5,     main_loop_1_exit
        {
            oper = 4;
            inst = new Instruction(oper, op, grammar[2], grammar[1]);
        } else if (op.equalsIgnoreCase("slt") || op.equalsIgnoreCase("seq")) // slt     r5,     r1,     3
        {
            oper = 5;
            inst = new Instruction(oper, op, grammar[1], grammar[2], grammar[3]);
//        } else if (op.equalsIgnoreCase("seq")) // seq     r5,     r1,     3
//        {
//            oper = 6;
//            inst = new Instruction(oper, op, grammar[1], grammar[2], grammar[3]);
        } else if (op.equalsIgnoreCase("addi")) // addi    r2,     r0,     1   or addi    r1,     r1,     r2
        {
            oper = 7;
            inst = new Instruction(oper, op, grammar[1], grammar[2], grammar[3]);
        } else if (op.equalsIgnoreCase("trap")) // trap 0
        {
            oper = 0;
            inst = new Instruction(oper, op, grammar[1]);
        }
        return inst;
    }

    private boolean isRegister(String str) {
        try {
            double d = Double.parseDouble(str);
        } catch (NumberFormatException nfe) {
            return true;
        }
        return false;
    }


}
